/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.sarl.maven.docs;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy.Type;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.settings.Proxy;
import org.arakhne.afc.util.ListUtil;
import org.arakhne.afc.vmutil.FileSystem;
import org.eclipse.xtext.util.JavaVersion;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.xbase.compiler.ImportManager;
import org.eclipse.xtext.xbase.compiler.output.FakeTreeAppendable;
import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.ComparisonFailure;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import io.sarl.lang.util.Utils;
import io.sarl.maven.docs.markdown.MarkdownParser;
import io.sarl.maven.docs.parser.AbstractMarkerLanguageParser;
import io.sarl.maven.docs.parser.DynamicValidationComponent;
import io.sarl.maven.docs.parser.DynamicValidationContext;
import io.sarl.maven.docs.parser.ValidationComponent;
import io.sarl.maven.docs.testing.DocumentationSetup;
import io.sarl.maven.docs.testing.ScriptExecutor;
/** Maven MOJO that is generating the documentation tests for the SARL project.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 0.6
*/
@Mojo(name = "generatetests", defaultPhase = LifecyclePhase.GENERATE_TEST_SOURCES,
requiresDependencyResolution = ResolutionScope.TEST)
public class GenerateTestsMojo extends AbstractDocumentationMojo {
private static final String BASE_PACKAGE = "io.sarl.maven.docs"; //$NON-NLS-1$
/**
* Indicates if the references to the local files should be validated.
*/
@Parameter(defaultValue = "true", required = false)
protected boolean localLinkValidation;
/**
* Indicates if the references to the remote Internet pages should be validated.
*/
@Parameter(defaultValue = "true", required = false)
protected boolean remoteLinkValidation;
/**
* Indicates if the timeout to use for connecting to remote hosts.
* This timeout value is expressed in milliseconds.
*/
@Parameter(defaultValue = "5000", required = false)
protected int remoteLinkTimeOut;
/**
* Indicates if a timeout for connecting to remote hosts should be ignored.
* If the timeout is ignore, the corresponding test will be marked as "ignored".
*
* @see Assume
*/
@Parameter(defaultValue = "true", required = false)
protected boolean ignoreRemoteLinkTimeOut;
/**
* Indicates if the references to the local files should be validated.
*/
@Parameter(defaultValue = "true", required = false)
protected boolean localImageValidation;
@Override
protected String getSkippingMessage() {
final String[] variableNames = {
"maven.test.skip", //$NON-NLS-1$
"skipTests", //$NON-NLS-1$
"io.sarl.docs.tests.skip", //$NON-NLS-1$
};
String value = null;
int i = 0;
while (Strings.isEmpty(value) && i < variableNames.length) {
value = System.getProperty(variableNames[i]);
++i;
}
i = 0;
while (Strings.isEmpty(value) && i < variableNames.length) {
value = System.getenv(variableNames[i]);
++i;
}
if (Boolean.valueOf(value)) {
return "Tests are skipped."; //$NON-NLS-1$
}
return null;
}
@Override
protected AbstractMarkerLanguageParser createLanguageParser(File inputFile) throws MojoExecutionException, IOException {
final AbstractMarkerLanguageParser parser = super.createLanguageParser(inputFile);
if (parser instanceof MarkdownParser) {
final MarkdownParser mdParser = (MarkdownParser) parser;
mdParser.setLocalImageReferenceValidation(this.localImageValidation);
mdParser.setLocalFileReferenceValidation(this.localLinkValidation);
mdParser.setRemoteReferenceValidation(
!this.session.isOffline() && this.remoteLinkValidation);
}
return parser;
}
@Override
protected String internalExecute(Map<File, File> files) {
getLog().info("Generating the testing resources"); //$NON-NLS-1$
final File output = FileSystem.convertStringToFile(this.testSourceDirectory);
final String msg = internalExecute(files, output);
if (!Strings.isEmpty(msg)) {
return msg;
}
try {
generateRunner(output);
generateAbstractTest(output);
return null;
} catch (IOException exception) {
final String message = Throwables.getRootCause(exception).getLocalizedMessage();
getLog().error(message);
getLog().debug(exception);
return message;
}
}
@Override
protected String internalExecute(Map<File, File> files, File outputFolder) {
try {
FileSystem.delete(outputFolder);
} catch (IOException exception) {
final String message = formatErrorMessage(outputFolder, exception);
getLog().error(message);
getLog().debug(exception);
return message;
}
return super.internalExecute(files, outputFolder);
}
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"})
@Override
protected void internalExecute(File sourceFolder, File inputFile, File relativeInputFile, File outputFolder,
AbstractMarkerLanguageParser parser) throws IOException {
final List<ValidationComponent> successCompilationComponents = new ArrayList<>();
final List<ValidationComponent> failureCompilationComponents = new ArrayList<>();
final List<ValidationComponent> factualComponents = new ArrayList<>();
for (final ValidationComponent component : parser.getStandardValidationComponents(inputFile)) {
if (component.isCompilable()) {
if (component.isExecutable()) {
factualComponents.add(component);
} else {
successCompilationComponents.add(component);
}
} else {
failureCompilationComponents.add(component);
}
}
final DynamicValidationContext validationContext = new DynamicValidationContext();
validationContext.setSourceRoots(this.session.getCurrentProject().getCompileSourceRoots());
validationContext.setResourceRoots(Lists.transform(this.session.getCurrentProject().getResources(),
(it) -> it.getDirectory()));
final List<DynamicValidationComponent> specificComponents = parser.getMarkerSpecificValidationComponents(
inputFile, sourceFolder, validationContext);
if (successCompilationComponents.isEmpty() && failureCompilationComponents.isEmpty()
&& factualComponents.isEmpty() && specificComponents.isEmpty()) {
return;
}
// Do not change the "Test" postfix because it is used by Surefire for detecting tests.
final String generalTestName = toTestName(inputFile) + "Test"; //$NON-NLS-1$
final ImportManager importManager = new ImportManager();
final ITreeAppendable it = new FakeTreeAppendable(importManager);
it.append("@").append(SuppressWarnings.class).append("(\"all\")"); //$NON-NLS-1$//$NON-NLS-2$
it.newLine();
it.append("@").append(RunWith.class).append("("); //$NON-NLS-1$ //$NON-NLS-2$
it.append(BASE_PACKAGE).append(".DocumentationTestRunner.class)"); //$NON-NLS-1$
it.newLine();
it.append("public class ").append(generalTestName).append(" extends "); //$NON-NLS-1$//$NON-NLS-2$
it.append(BASE_PACKAGE).append(".AbstractBaseTest {"); //$NON-NLS-1$
it.increaseIndentation();
int i = 0;
for (final ValidationComponent component : successCompilationComponents) {
final String actionName = "success_" + component.getLineno() + "_" + i; //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("@").append(Test.class); //$NON-NLS-1$
it.newLine();
it.append("public void ").append(actionName).append("() throws Exception {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append(List.class).append("<String> issues = getScriptExecutor().compile("); //$NON-NLS-1$
it.append(Integer.toString(component.getLineno())).append(", \""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(component.getCode()));
it.append("\");"); //$NON-NLS-1$
it.newLine();
it.append("assertNoIssue(" + component.getLineno() + ", issues);"); //$NON-NLS-1$ //$NON-NLS-2$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
++i;
}
i = 0;
for (final ValidationComponent component : failureCompilationComponents) {
final String actionName = "failure_" + component.getLineno() + "_" + i; //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("@").append(Test.class); //$NON-NLS-1$
it.newLine();
it.append("public void ").append(actionName).append("() throws Exception {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append(List.class).append("<String> issues = getScriptExecutor().compile("); //$NON-NLS-1$
it.append(Integer.toString(component.getLineno())).append(", \""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(component.getCode()));
it.append("\");"); //$NON-NLS-1$
it.newLine();
it.append("assertIssues(" + component.getLineno() + ", issues);"); //$NON-NLS-1$ //$NON-NLS-2$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
++i;
}
i = 0;
for (final ValidationComponent component : factualComponents) {
final String actionName = "fact_" + component.getLineno() + "_" + i; //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("@").append(Test.class); //$NON-NLS-1$
it.newLine();
it.append("public void ").append(actionName).append("() throws Exception {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("final String expected = ").append(Utils.class).append(".dump(Boolean.TRUE, false) + "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"\\nOR\\nObject {\\n}\\n\";"); //$NON-NLS-1$
it.newLine();
it.append("Object result;"); //$NON-NLS-1$
it.newLine();
it.append("try {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("result = getScriptExecutor().execute(").append(Integer.toString(component.getLineno())); //$NON-NLS-1$
it.append(", \"").append(Strings.convertToJavaString(component.getCode())); //$NON-NLS-1$
it.append("\");"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("} catch (Throwable exception) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("throw new ").append(ComparisonFailure.class); //$NON-NLS-1$
it.append("(exception.getLocalizedMessage() + \" [line: ").append(Integer.toString(component.getLineno())); //$NON-NLS-1$
it.append("]\", expected, ").append(Throwables.class); //$NON-NLS-1$
it.append(".getStackTraceAsString(exception));"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("if (result instanceof Boolean) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("boolean boolResult = ((Boolean) result).booleanValue();"); //$NON-NLS-1$
it.newLine();
it.append("if (!boolResult) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("throw new ").append(ComparisonFailure.class); //$NON-NLS-1$
it.append("(\"Invalid expression result [line: ").append(Integer.toString(component.getLineno())); //$NON-NLS-1$
it.append("]\", expected, ").append(Utils.class); //$NON-NLS-1$
it.append(".dump(result, false));"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("} else if (result == null || result instanceof Exception) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("throw new ").append(ComparisonFailure.class); //$NON-NLS-1$
it.append("(\"Invalid expression result [line: ").append(Integer.toString(component.getLineno())); //$NON-NLS-1$
it.append("]\", expected, ").append(Utils.class); //$NON-NLS-1$
it.append(".dump(result, false));"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
++i;
}
i = 0;
for (final DynamicValidationComponent component : specificComponents) {
final String actionName = component.functionName() + i;
it.newLine();
it.append("@").append(Test.class); //$NON-NLS-1$
it.newLine();
it.append("public void ").append(actionName).append("() throws Exception {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
component.generateValidationCode(it);
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
++i;
}
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
final File packagePath = relativeInputFile.getParentFile();
final String packageName = toPackageName("docs", packagePath); //$NON-NLS-1$
write(outputFolder, packageName, generalTestName, importManager, it);
}
@SuppressWarnings({"checkstyle:methodlength", "checkstyle:npathcomplexity"})
private void generateAbstractTest(File outputFolder) throws IOException {
getLog().debug("Generating abstract test"); //$NON-NLS-1$
final ImportManager importManager = new ImportManager();
final ITreeAppendable it = new FakeTreeAppendable(importManager);
it.append("@").append(SuppressWarnings.class).append("(\"all\")"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("public class AbstractBaseTest {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("protected static String STR_SUCCESS = \"success\";"); //$NON-NLS-1$
it.newLine();
it.append("protected static String STR_FAILURE = \"failure\";"); //$NON-NLS-1$
it.newLine();
it.append("protected static String STR_FACT = \"fact\";"); //$NON-NLS-1$
it.newLine().newLine();
it.append("private static ").append(Injector.class).append(" injector = "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(DocumentationSetup.class).append(".doSetup();"); //$NON-NLS-1$
it.newLine().newLine();
it.append("private ").append(ScriptExecutor.class).append(" scriptExecutor;"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine().newLine();
it.append("protected ").append(ScriptExecutor.class).append(" getScriptExecutor() {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("if (this.scriptExecutor == null) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("this.scriptExecutor = this.injector.getInstance(").append(ScriptExecutor.class).append(".class);"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
final StringBuilder cp = new StringBuilder();
for (final File cpElement : getClassPath()) {
if (cp.length() > 0) {
cp.append(":"); //$NON-NLS-1$
}
cp.append(cpElement.getAbsolutePath());
}
it.append("scriptExecutor.setClassPath(\""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(cp.toString()));
it.append("\");"); //$NON-NLS-1$
it.newLine();
final String bootPath = getBootClassPath();
if (!Strings.isEmpty(bootPath)) {
it.append("scriptExecutor.setBootClassPath(\""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(bootPath));
it.append("\");"); //$NON-NLS-1$
it.newLine();
}
JavaVersion version = null;
if (!Strings.isEmpty(this.source)) {
version = JavaVersion.fromQualifier(this.source);
}
if (version == null) {
version = JavaVersion.JAVA8;
}
it.append("scriptExecutor.setJavaSourceVersion(\""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(version.getQualifier()));
it.append("\");"); //$NON-NLS-1$
it.newLine();
it.append("scriptExecutor.setTempFolder(").append(FileSystem.class); //$NON-NLS-1$
it.append(".convertStringToFile(\""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(this.tempDirectory.getAbsolutePath()));
it.append("\"));"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("return this.scriptExecutor;"); //$NON-NLS-1$
it.newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public void assertNoIssue(int lineno, ").append(List.class).append("<String> issues) {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("if (issues != null && !issues.isEmpty()) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(StringBuilder.class).append(" msg = new ").append(StringBuilder.class).append("();"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("for (String message : issues) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("msg.append(message).append(\"\\n\");"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("throw new ").append(ComparisonFailure.class).append(//$NON-NLS-1$
"(\"Expecting no issue but find one [line:\" + lineno + \"]\", \"\", msg.toString());"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public void assertIssues(int lineno, ").append(List.class).append("<String> issues) {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("if (issues == null || issues.isEmpty()) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(Assert.class).append(".fail(\"Expecting issues but did not find one [line:\" + lineno + \"]\");"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public static String getHttpCodeExplanation(int code) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("switch (code) {"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_BAD_METHOD: return \"Method Not Allowed\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_CREATED: return \"Created\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_ACCEPTED: return \"Accepted\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NOT_AUTHORITATIVE: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Non-Authoritative Information\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NO_CONTENT: return \"No Content\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_RESET: return \"Reset Content\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_PARTIAL: return \"Partial Content\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_MULT_CHOICE: return \"Multiple Choices\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_MOVED_PERM: return \"Moved Permanently\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_MOVED_TEMP: return \"Temporary Redirect\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_SEE_OTHER: return \"See Other\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NOT_MODIFIED: return \"Not Modified\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_USE_PROXY: return \"Use Proxy\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_BAD_REQUEST: return \"Bad Request\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_UNAUTHORIZED: return \"Unauthorized\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_PAYMENT_REQUIRED: return \"Payment Required\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_FORBIDDEN: return \"Forbidden\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NOT_FOUND: return \"Not Found\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NOT_ACCEPTABLE: return \"Not Acceptable\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_PROXY_AUTH: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Proxy Authentication Required\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_CLIENT_TIMEOUT: return \"Request Time-Out\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_CONFLICT: return \"Conflict\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_GONE: return \"Gone\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_LENGTH_REQUIRED: return \"Length Required\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_PRECON_FAILED: return \"Precondition Failed\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_ENTITY_TOO_LARGE: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Request Entity Too Large\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_REQ_TOO_LONG: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Request-URI Too Large\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_UNSUPPORTED_TYPE: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Unsupported Media Type\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_INTERNAL_ERROR: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"Internal Server Error\";"); //$NON-NLS-1$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_NOT_IMPLEMENTED: return \"Not Implemented\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_BAD_GATEWAY: return \"Bad Gateway\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_UNAVAILABLE: return \"Service Unavailable\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_GATEWAY_TIMEOUT: return \"Gateway Timeout\";"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("case ").append(HttpURLConnection.class).append(".HTTP_VERSION: return "); //$NON-NLS-1$ //$NON-NLS-2$
it.append("\"HTTP Version Not Supported\";"); //$NON-NLS-1$
it.newLine();
it.append("default: return null;"); //$NON-NLS-1$
it.newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public static boolean isAcceptableHttpCode(int code) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("return code == ").append(HttpURLConnection.class).append(".HTTP_OK"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("|| code == ").append(HttpURLConnection.class).append(".HTTP_MOVED_TEMP"); //$NON-NLS-1$ //$NON-NLS-2$
it.decreaseIndentation().newLine();
it.append(";"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public static void assertURLAccessibility(int lineno, ").append(URL.class); //$NON-NLS-1$
it.append(" url) throws ").append(Exception.class).append(" {"); //$NON-NLS-1$//$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("int code;"); //$NON-NLS-1$
it.newLine();
it.append(HttpURLConnection.class).append(".setFollowRedirects(false);"); //$NON-NLS-1$
it.newLine();
it.append(URLConnection.class).append(" connection = url.openConnection();"); //$NON-NLS-1$
it.newLine();
it.append(Assume.class).append(".assumeTrue(\"Not an UTL with http[s] protocol\", connection instanceof "); //$NON-NLS-1$
it.append(HttpURLConnection.class).append(");"); //$NON-NLS-1$
it.newLine();
it.append(HttpURLConnection.class).append(" httpConnection = ("); //$NON-NLS-1$
it.append(HttpURLConnection.class).append(") connection;"); //$NON-NLS-1$
it.newLine();
it.append("try {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("httpConnection.setInstanceFollowRedirects(false);"); //$NON-NLS-1$
it.newLine();
it.append("httpConnection.setConnectTimeout(").append(Integer.toString(this.remoteLinkTimeOut)); //$NON-NLS-1$
it.append(");"); //$NON-NLS-1$
it.newLine();
it.append("httpConnection.setReadTimeout(").append(Integer.toString(this.remoteLinkTimeOut)); //$NON-NLS-1$
it.append(");"); //$NON-NLS-1$
it.newLine();
it.append("httpConnection.setAllowUserInteraction(false);"); //$NON-NLS-1$
it.newLine();
it.append("httpConnection.setRequestMethod(\"HEAD\");"); //$NON-NLS-1$
it.newLine();
it.append("httpConnection.connect();"); //$NON-NLS-1$
it.newLine();
it.append("code = httpConnection.getResponseCode();"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("} catch (").append(IOException.class).append(" exception) {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("Throwable rootCause = ").append(Throwables.class).append(".getRootCause(exception);"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
if (this.ignoreRemoteLinkTimeOut) {
it.append("if (rootCause instanceof ").append(SocketTimeoutException.class).append(") {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append(Assume.class).append(".assumeNoException(\""); //$NON-NLS-1$
it.append("Connection time-out at line \" + lineno + \" when connecting to: \" + url.toExternalForm()"); //$NON-NLS-1$
it.append(", rootCause);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
}
it.append("throw new ").append(RuntimeException.class); //$NON-NLS-1$
it.append("(\"Error at line \" + lineno + \" when connecting to: \" + url.toExternalForm(), rootCause);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("} finally {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("httpConnection.disconnect();"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("if (isAcceptableHttpCode(code)) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("return;"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("String explanation = getHttpCodeExplanation(code);"); //$NON-NLS-1$
it.newLine();
it.append("String codeMsg = !").append(Strings.class); //$NON-NLS-1$
it.append(".isEmpty(explanation) ? code + \"/\\\"\" + explanation + \"\\\"\" : Integer.toString(code);"); //$NON-NLS-1$
it.newLine();
it.append(Assert.class).append(".fail(\"Invalid response code \" + codeMsg + \" at line \" + lineno "); //$NON-NLS-1$
it.append("+ \" when connecting to: \" + url.toExternalForm());"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
if (!this.session.isOffline() && !this.session.getRequest().getProxies().isEmpty()) {
it.newLine().newLine();
it.append("private static boolean proxyNameMatches(String pattern, String name) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(Pattern.class).append(" pat = "); //$NON-NLS-1$
it.append(Pattern.class).append(".compile(pattern, "); //$NON-NLS-1$
it.append(Pattern.class).append(".CASE_INSENSITIVE);"); //$NON-NLS-1$
it.newLine();
it.append(Matcher.class).append(" mat = pat.matcher(name);"); //$NON-NLS-1$
it.newLine();
it.append("return mat.matches();"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("static {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("final ").append(ProxySelector.class).append(" defaultSelector = "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(ProxySelector.class).append(".getDefault();"); //$NON-NLS-1$
it.newLine();
it.append(ProxySelector.class).append(" newSelector = new ").append(ProxySelector.class); //$NON-NLS-1$
it.append("() {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("public ").append(List.class); //$NON-NLS-1$
it.append("<").append(java.net.Proxy.class).append("> select(final "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(URI.class).append(" uri) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(List.class).append("<").append(java.net.Proxy.class).append("> proxies = new "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(ArrayList.class).append("(defaultSelector.select(uri));"); //$NON-NLS-1$
it.newLine();
for (final Proxy proxy : this.session.getRequest().getProxies()) {
it.append("if (\"").append(Strings.convertToJavaString(proxy.getProtocol())); //$NON-NLS-1$
it.append("\".equals(uri.getScheme())) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
final String nonProxyHosts = proxy.getNonProxyHosts();
boolean hasProxy = false;
if (!Strings.isEmpty(nonProxyHosts)) {
if (nonProxyHosts != null) {
hasProxy = true;
it.append("if ("); //$NON-NLS-1$
final StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|"); //$NON-NLS-1$
boolean first = true;
while (tokenizer.hasMoreTokens()) {
String pattern = tokenizer.nextToken();
pattern = pattern.replace(".", "\\.").replace("*", ".*"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
if (first) {
first = false;
} else {
it.newLine().append(" && "); //$NON-NLS-1$
}
it.append("!proxyNameMatches(\"^").append(Strings.convertToJavaString(pattern)); //$NON-NLS-1$
it.append("$\", uri.getHost())"); //$NON-NLS-1$
}
it.append(") {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
}
}
it.append("proxies.add(new ").append(java.net.Proxy.class); //$NON-NLS-1$
it.append("(").append(Type.class).append(".HTTP, new "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(InetSocketAddress.class).append("(\""); //$NON-NLS-1$
it.append(Strings.convertToJavaString(proxy.getHost()));
it.append("\", ").append(Integer.toString(proxy.getPort())); //$NON-NLS-1$
it.append(")));"); //$NON-NLS-1$
if (hasProxy) {
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
}
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
}
it.append("return ").append(Collections.class).append(".unmodifiableList(proxies);"); //$NON-NLS-1$ //$NON-NLS-2$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("public void connectFailed(").append(URI.class); //$NON-NLS-1$
it.append(" uri, ").append(SocketAddress.class); //$NON-NLS-1$
it.append(" sa, ").append(IOException.class); //$NON-NLS-1$
it.append(" ioe) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("throw new ").append(RuntimeException.class); //$NON-NLS-1$
it.append("(ioe);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("};"); //$NON-NLS-1$
it.newLine();
it.append(ProxySelector.class).append(".setDefault(newSelector);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
}
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
write(outputFolder,
BASE_PACKAGE, "AbstractBaseTest", //$NON-NLS-1$
importManager, it);
}
private void generateRunner(File outputFolder) throws IOException {
getLog().debug("Generating test runner"); //$NON-NLS-1$
final ImportManager importManager = new ImportManager();
final ITreeAppendable it = new FakeTreeAppendable(importManager);
it.append("@").append(SuppressWarnings.class).append("(\"all\")"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("public class DocumentationTestRunner extends ").append(BlockJUnit4ClassRunner.class).append(" {"); //$NON-NLS-1$//$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("protected ").append(Pattern.class).append(" pattern = "); //$NON-NLS-1$ //$NON-NLS-2$
it.append(Pattern.class).append(".compile(\"^((?:success)|(?:failure)|(?:fact))\\\\_([0-9]+)\\\\_\");"); //$NON-NLS-1$
it.newLine();
it.append("protected String testName(").append(FrameworkMethod.class).append(" method) {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append("String methodName = method.getMethod().getName();"); //$NON-NLS-1$
it.newLine();
it.append(Matcher.class).append(" matcher = pattern.matcher(methodName);"); //$NON-NLS-1$
it.newLine();
it.append("if (matcher.find()) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("String typeName = matcher.group(1);"); //$NON-NLS-1$
it.newLine();
it.append("int lineno = Integer.valueOf(matcher.group(2));"); //$NON-NLS-1$
it.newLine();
it.append("if (\"success\".equals(typeName)) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("return \"Success block #\" + lineno;"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.append("if (\"failure\".equals(typeName)) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("return \"Failing block #\" + lineno;"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.append("if (\"fact\".equals(typeName)) {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("return \"Factual expression #\" + lineno;"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.append("return methodName.replaceAll(\"[_]+\", \" \");"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("protected ").append(List.class).append("<"); //$NON-NLS-1$ //$NON-NLS-2$
it.append(FrameworkMethod.class).append("> getChildren() {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(List.class).append("<").append(FrameworkMethod.class); //$NON-NLS-1$
it.append("> sortedList = new ").append(ArrayList.class).append("<>();"); //$NON-NLS-1$ //$NON-NLS-2$
it.newLine();
it.append("for (").append(FrameworkMethod.class).append(" method : computeTestMethods()) {"); //$NON-NLS-1$ //$NON-NLS-2$
it.increaseIndentation().newLine();
it.append(ListUtil.class).append(".add(sortedList, (c1, c2) -> {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append(Matcher.class).append(" matcher1 = pattern.matcher(c1.getName());"); //$NON-NLS-1$
it.newLine();
it.append("if (!matcher1.find()) return c1.getName().compareTo(c2.getName());"); //$NON-NLS-1$
it.newLine();
it.append(Matcher.class).append(" matcher2 = pattern.matcher(c2.getName());"); //$NON-NLS-1$
it.newLine();
it.append("if (!matcher2.find()) return c1.getName().compareTo(c2.getName());"); //$NON-NLS-1$
it.newLine();
it.append("int cmp = Integer.compare(Integer.valueOf(matcher1.group(2)), Integer.valueOf(matcher1.group(2)));"); //$NON-NLS-1$
it.newLine();
it.append("if (cmp != 0) return cmp;"); //$NON-NLS-1$
it.newLine();
it.append("return c1.getName().compareTo(c2.getName());"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}, method, true, false);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
it.append("return sortedList;"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine().newLine();
it.append("public DocumentationTestRunner(Class<?> clazz) throws "); //$NON-NLS-1$
it.append(InitializationError.class).append(" {"); //$NON-NLS-1$
it.increaseIndentation().newLine();
it.append("super(clazz);"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.decreaseIndentation().newLine();
it.append("}"); //$NON-NLS-1$
it.newLine();
write(outputFolder,
BASE_PACKAGE, "DocumentationTestRunner", //$NON-NLS-1$
importManager, it);
}
private static void write(File root, String packageName, String typeName, ImportManager importManager,
ITreeAppendable it) throws IOException {
final File relativeFolder = toPackageFolder(packageName);
File outputFile = FileSystem.join(root, relativeFolder);
outputFile.mkdirs();
outputFile = new File(outputFile, typeName + ".java"); //$NON-NLS-1$
try (FileWriter writer = new FileWriter(outputFile)) {
writer.write("/* This file was automatically generated. Do not change its content. */\n\n"); //$NON-NLS-1$
writer.write("package "); //$NON-NLS-1$
writer.write(packageName);
writer.write(";\n"); //$NON-NLS-1$
for (final String importedType : importManager.getImports()) {
writer.write("import "); //$NON-NLS-1$
writer.write(importedType);
writer.write(";\n"); //$NON-NLS-1$
}
writer.write(it.getContent());
writer.flush();
}
}
private static String toTestName(File inputFile) {
return Strings.toFirstUpper(FileSystem.shortBasename(inputFile).replaceAll("[^a-zA-Z0-9]+", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
}